/*
 * FileName : lifeGame.cpp
 * A little game for cell evolution.
 */
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
#include <vector>
#include <fstream>

using namespace std;

const char SPACE = '#';
const char CELL = '*';

void printUsage() {
     cout << "LifeGame Usage: lifeGame -n N [-f filename]\n"
          << "                -n       the times of evolution(0 < N < 65530)\n"
          << "                -f       input filename\n"
          << endl; 
}

void printHelp() {
      // introduce 
    cout << "                            LifeGame\n"
         << "Rules:\n"
         << "  The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, \
each of which is in one of two possible states, live or dead. Every cell interacts with its, \
eight neighbors, which are the cells that are directly horizontally, vertically, or diagonally \
adjacent. At each step in time, the following transitions occur:\n\n"
         << "1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation. \n"
         << "2. Any live cell with more than three live neighbours dies, as if by overcrowding. \n"
         << "3. Any live cell with two or three live neighbours lives on to the next generation. \n"
         << "4. Any dead cell with exactly three live neighbours becomes a live cell. \n"
         << endl;
}

// check if the string is a integer
// @paramter    nums(char *):point to the string need be check
// @return      (bool):true is a integer;false not
bool checkNum(char * nums) {
     for(int i = 0; nums[i] != '\0'; i++) {
             if(nums[i] > '9' || nums[i] < '0') 
                        return false;
     }
     
     return true;
}

// handle the cmd line arguments
// @paramter      argc(int):number of arguments
// @paramter      argv(char **):arguments
// @return        (int):-1 for invalid; other for times
int handleArgs(int argc, char * argv[], string* filename) {
    int times;
    // not 3 args
    if( 2 == argc ) {
         if(0 == strcmp(argv[1], "-h")) {
              printUsage();
              printHelp();
              return -1;
         } else {
              printUsage();
              return -1;
         }
    }
    
    if( 3 == argc || 5 == argc) {
        if(0 == strcmp(argv[1], "-n")) {
             if(checkNum(argv[2])) {
                  times = atoi(argv[2]);
             } else {
                  printUsage();
                  return -1;
             }
        } else {
             printUsage();
             return -1;
        }
        
        if( 5 == argc) {
            if(0 == strcmp(argv[3], "-f")) {
                string tempStr(argv[4]);
                if(tempStr.length() == 0) {
                      printUsage();
                      return -1;
                } else {
                      *filename = tempStr;
                }
            } else {
                printUsage();
                return -1;
            }
        }
    }

    // number is too big or too samlle
    if(65530 < times || 0 > times) {
             printUsage();
             return -1;
    }
    
    return times;
}

// check the valid input 
// strings only include '#' or '*' are valid
// @paramter    tempStr(string):the string need to be checked
// @return      (int):0 for invalid; 1 for valid 
int checkValid(string tempStr) {
    if(tempStr.size() >= 80 || tempStr.size() < 2) {
                      return 0;
    }
    
    for(int i = 0; i < tempStr.size(); i++) {
            if(tempStr[i] != SPACE && tempStr[i] != CELL)
                          return 0;
    }
    
    return 1;
}

// find the lived cell in right line
// @paramter      tempStr(string):the line need to be find
// @paramter      line(int):the centre position to be find
// @paramter      self(bool):need to check the centre position; false to check; true to uncheck 
// @return        (int):number of lived cells
int liveCell(string tempStr, int line, bool self) {
    int strLen = tempStr.length();
    int res = 0;
    if((line - 1) >= 0 && (line - 1) < strLen && tempStr[line - 1] == CELL)
             res++;
    if((line + 1) >= 0 && (line + 1) < strLen && tempStr[line + 1] == CELL)
             res++;
    if(self == false) {
            if((line) >= 0 && (line) < strLen && tempStr[line] == CELL)
                      res++;
    }
    
    return res;  
} 

// main evolution 
// @parameter     buffer(vectro<string>):the image of buffer
// @parameter     rowChangedP(vector<int>*):point to the rowChanged
// @parameter     lineChangedP(vector<int>*):point to the lineChanged
// @return        (bool)true for has changed or lived cell; false for no
bool evolution(vector<string> *buffer, vector<int> *rowChangedP, vector<int> *lineChangedP) {
  // each row
  string tempStr;
  const int MAXROW = buffer->size();
  int maxLine;
  int numOfCell;
  bool hasChanged = false;

  for(int row = 0; row < MAXROW; row++) {
          tempStr = buffer->at(row);
          string upStr;
          string downStr;
                    
          if(row > 0) 
                 upStr = buffer->at(row - 1);
          else
                 upStr = string(85, SPACE);
          if(row < MAXROW - 1) 
                 downStr = buffer->at(row + 1);
          else
                 downStr = string(85, SPACE);
                           
          int maxLine = tempStr.length();
          // each line
          for(int line = 0; line < maxLine; line++) {
                   int numOfCell = 0;
                   numOfCell += liveCell(upStr, line, false);
                   numOfCell += liveCell(tempStr, line, true);
                   numOfCell += liveCell(downStr, line, false);
                   //cout << numOfCell << " ";
                   if(CELL == tempStr.at(line)) {
                           
						   if(numOfCell < 2 || numOfCell > 3) {
                           // save the changed
                              rowChangedP->push_back(row);
                              lineChangedP->push_back(line);
                              if(false == hasChanged)
                                    hasChanged = true;
                           }
						                       
                   } else {
                                if(numOfCell == 3) {
                                // save the changed
                                   rowChangedP->push_back(row);
                                   lineChangedP->push_back(line);
                                
                                   if(false == hasChanged)
                                            hasChanged = true;
                                }
                  }
          }
  }
  
  if( hasChanged == true )
              return true;
  else
              return false;
}

// main
int main(int argc, char * argv[]) {
    string filename;
    int times = handleArgs(argc, argv, &filename);
    string tempStr;
    int numOfEvolution = 0;      // times of the evolution
    int  MAXROW;                 // total row in the buffer
    vector<string> buffer;       // store the init state
    vector<int> rowChanged;      // store each time changed row
    vector<int> lineChanged;     // store each time changed line
    bool needInput = true;       // do we need read from files?
    
    if(-1 == times)
         exit(0);
    // read from file     
    if(filename.length() != 0) {
           ifstream fin(filename.c_str());
           if(!fin) {
                 cout << "can't open file: " << filename << endl;
           } else {
                 // read and check
                 while(getline(fin,tempStr)){     
                      if(1 == checkValid(tempStr)) {
                          buffer.push_back(tempStr); 
                      }
                 }  
                 fin.close();                     
                 needInput = false;
           }
    }
    // do we need to input?
    if(needInput) {
        cout << "Please input the init state:\n"
             << "Notice: the length of one line must 2 < length < 80, the only valid input is '#' and '*':\n"
             << "'#' for space\n"
             << "'*' for live cell\n"
             << endl;
        // read the init state        
        while(1) {
                 std::cin >> tempStr;
                 //if "--" the input stop
                 if(0 == strcmp(tempStr.c_str(), "--")) {
                      break;
                 }
                 //if have invalid input input again
                 if(0 == checkValid(tempStr)) {
                      cout << "The last input line was not valid, please input again" << endl;
                      continue;
                 }
                 // save the input into buffer
                 buffer.push_back(tempStr);
        }
    }
    // get the max row
    MAXROW = buffer.size();
    // main circle
    while(numOfEvolution < times) {
            bool needStop = evolution(&buffer, &rowChanged, &lineChanged);
            // make changed
            for(int i = 0; i < rowChanged.size(); i++) {
                    if(CELL == buffer[rowChanged[i]][lineChanged[i]])
                            buffer[rowChanged[i]][lineChanged[i]] = SPACE;
                    else
                            buffer[rowChanged[i]][lineChanged[i]] = CELL;
                                                             
            }
            // print now generation
            for(int row = 0; row < MAXROW; row++) {
                    cout << buffer.at(row) << endl;
            }
            // the seperate line
            cout << endl;
            
            // if no changed or no lived stop
            if(!needStop) {
                    break;
            }
            // clear last change
            rowChanged.clear();
            lineChanged.clear();
            // evolution move on
            numOfEvolution++;
    }
    
    //cout << buffer.size();
    
    //system("pause");
}
// End of lifeGame.cpp
